package geecache

import (
	"fmt"
	"geecache/consistenthash"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"sync"
)

const (
	defaultBasePath = "/_geecache/"
	defaultReplicas = 50
)

type httpGetter struct {
	baseURL string
}

func (h *httpGetter) Get(group string,key string) ([]byte,error) {
	u := fmt.Sprintf("%v%v/%v",h.baseURL,url.QueryEscape(group),url.QueryEscape(key))
	res,err := http.Get(u)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	
	if res.StatusCode != http.StatusOK {
		return nil,fmt.Errorf("server returned: %v",res.Status)
	}
	
	bytes,err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil,fmt.Errorf("reading response body: %v",err)
	}
	return bytes,nil
}
//这是确保接口被实现常用的方式。即利用强制类型转换，确保 struct HTTPPool 实现了接口 PeerPicker。
//这样 IDE 和编译期间就可以检查，而不是等到使用的时候。
var _ PeerGetter = (*httpGetter)(nil)

/*
创建一个结构体 HTTPPool，作为承载节点间 HTTP 通信的核心数据结构
*/
type HTTPPool struct {
	//用来记录自己的地址，包括主机名/IP 和端口
	self string
	//节点间通讯地址的前缀，默认是 /_geecache/
	basePath string
	mu sync.Mutex
	peers *consistenthash.Map
	//成员变量 httpGetters，映射远程节点与对应的 httpGetter。
	//每一个远程节点对应一个 httpGetter，因为 httpGetter 与远程节点的地址 baseURL 有关
	httpGetters map[string]*httpGetter
}

func NewHTTPPool(self string) *HTTPPool {
	return &HTTPPool{
		self:    	self,
		basePath: 	defaultBasePath,
	}
}

func (p *HTTPPool) Log(format string,v ...interface{})  {
	log.Printf("[Server %s] %s",p.self,fmt.Sprintf(format,v...))
}

func (p *HTTPPool) ServeHTTP(w http.ResponseWriter,r *http.Request)  {
	if !strings.HasPrefix(r.URL.Path,p.basePath){
		panic("HTTPPool serving unexcepting path: "+r.URL.Path)
	}
	p.Log("%s %s",r.Method,r.URL.Path)
	//约定访问路径格式为 /<basepath>/<groupname>/<key>
	//通过 groupname 得到 group 实例，再使用 group.Get(key) 获取缓存数据
	parts := strings.SplitN(r.URL.Path[len(p.basePath):],"/",2)
	if len(parts) != 2 {
		http.Error(w,"bad request",http.StatusBadRequest)
		return
	}

	groupName := parts[0]
	key := parts[1]

	group := GetGroup(groupName)
	if group == nil {
		http.Error(w,"no such group: "+groupName,http.StatusNotFound)
		return
	}

	view, err := group.Get(key)
	if err != nil {
		http.Error(w,err.Error(),http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type","application/octet-stream")
	_, _ = w.Write(view.ByteSlice())

}

func (p *HTTPPool) Set(peers ...string)  {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.peers = consistenthash.New(defaultReplicas,nil)
	p.peers.Add(peers...)
	p.httpGetters = make(map[string]*httpGetter,len(peers))
	for _,peer := range peers {
		p.httpGetters[peer] = &httpGetter{baseURL: peer+p.basePath}
	}
}

func (p *HTTPPool) PickPeer(key string) (PeerGetter,bool) {
	p.mu.Lock()
	defer p.mu.Unlock()
	if peer := p.peers.Get(key);peer != "" && peer != p.self {
		p.Log("Pick peer %s",peer)
		return p.httpGetters[peer],true
	}
	return nil,false
}

var _ PeerPicker = (*HTTPPool)(nil)